package com.starleague.common;

import java.util.Collection;
import java.util.Map;

import com.google.common.base.Strings;

public class Stringifier
{
    protected static final String INDENT = "    ";
    protected static final String ROOT_INDENT = "\n" + INDENT;

    private final IChildVisitable rootVisitable;
    private final int depth;

    public static String toString(IChildVisitable visitable,
                                  int depth)
    {
        return (new Stringifier(visitable, depth)).toString();
    }

    public Stringifier(IChildVisitable rootVisitable,
                       int depth)
    {
        this.rootVisitable = rootVisitable;
        this.depth = depth;
    }

    @Override
    public String toString()
    {
        StringBuilder result = new StringBuilder(getChildVisitableName(this.rootVisitable));

        this.rootVisitable.accept(new StringifierVisitor(this.depth, result));

        return "\n" + ROOT_INDENT + result.toString() + "\n";
    }

    protected String getChildVisitableName(IChildVisitable visitable)
    {
        String className = visitable.getClass().getSimpleName();

        return !Strings.isNullOrEmpty(className) ? className : visitable.getClass().getName() + " (IVisitable)";
    }

    private final class StringifierVisitor
            extends AbstractChildVisitor
    {
        private final int maxDepth;
        private int currentDepth;
        private final StringBuilder result;

        public StringifierVisitor(int depth,
                                  StringBuilder result)
        {
            this.maxDepth = this.currentDepth = depth;
            this.result = result;
        }

        @Override
        public <KEY, VALUE> void visit(KEY key,
                                       VALUE value)
        {
            this.currentDepth--;
            super.visit(key, value);
            this.currentDepth++;
        }

        @Override
        protected <KEY, VALUE> void visitValue(KEY key,
                                               VALUE value)
        {
            append(key, value);
        }

        @Override
        protected <KEY> void visitChildVisitable(KEY key,
                                                 IChildVisitable visitable)
        {
            append(key, getChildVisitableName(visitable));
            if (this.currentDepth >= 0)
            {
                super.visitChildVisitable(key, visitable);
            }
        }

        @Override
        protected <KEY, VALUE> void visitCollection(KEY key,
                                                    Collection<? extends VALUE> collection)
        {
            append(key, collection.getClass().getSimpleName() + " [" + collection.size() + "] ");
            if (this.currentDepth >= 0)
            {
                super.visitCollection(key, collection);
            }
        }

        @Override
        protected <KEY, VALUE> void visitArray(KEY key,
                                               VALUE[] array)
        {
            append(key, "Array [" + array.length + "]");
            if (this.currentDepth >= 0)
            {
                super.visitArray(key, array);
            }
        }

        @Override
        protected <KEY, SUBKEY, VALUE> void visitMap(KEY key,
                                                     Map<? extends SUBKEY, ? extends VALUE> map)
        {
            append(key, "Map [" + map.size() + "] ");
            if (this.currentDepth >= 0)
            {
                super.visitMap(key, map);
            }
        }

        private <KEY, VALUE> void append(KEY key,
                                         VALUE value)
        {
            this.result.append(ROOT_INDENT
                               + Strings.repeat(INDENT, this.maxDepth - this.currentDepth)
                               + "["
                               + key
                               + "] = "
                               + value);
        }
    }
}
